package org.dreamwork.jasmine2.javac;

import org.apache.log4j.Logger;
import org.dreamwork.compilation.*;
import org.dreamwork.jasmine2.annotation.*;
import org.dreamwork.jasmine2.javac.configuration.ConfigurationParser;
import org.dreamwork.jasmine2.javac.configuration.ControlConfiguration;
import org.dreamwork.jasmine2.parser.*;
import org.dreamwork.jasmine2.template.CodeContext;
import org.dreamwork.util.StringUtil;
import org.eclipse.jdt.internal.compiler.batch.Main;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by IntelliJ IDEA.
 * User: seth
 * Date: 2009-1-4
 * Time: 17:04:30
 */
public class JasmineCreator2 {
    private Reader reader;
    private Tag root;
    private JasminePageInfo pageInfo;
    private JasmineFileInfo fileInfo;

    private Map<String, Integer> declareControls = new HashMap<String, Integer> ();
    private Map<String, Map<String, Method>> cache = new HashMap<String, Map<String, Method>> ();
    private Map<String, String> userControls = new HashMap<String, String> ();
    private Stack<String> stack = new Stack<String> ();
    private VirtualMethod initComponents;
    private VirtualConstruction construction;
    private PageInitMethod init;
    private VirtualClass vc = new VirtualClass ();
    private Class superClass;
    private File userControlClassPath;
    private CodeContext context = new CodeContext ();

    private static String compileClassPath;

    private static final Logger logger = Logger.getLogger (JasmineCreator2.class);
    private static final String[] special_attrs = {"runat", "id"};
    private static final Pattern p_context_root = Pattern.compile ("^\"?\\s*~/(.+)\"?$");
    private static final Pattern P_expression = Pattern.compile ("<%(=|#)(.*?)%>");

    private static ClassLoader loader = null;
    private static Set<URL> compileUrls = Collections.synchronizedSet (new HashSet<URL> ());
    private static final Object locker = new Object ();

    private static class PageInitMethod extends VirtualMethod {
        private List<String> blocks = new ArrayList<String> ();

        public String toString () {
            StringBuilder buff = new StringBuilder ();
            buff.append ("public void init () {\r\n");
            buff.append ("try {\r\n");
            for (Object block : blocks)
                buff.append (block).append ("\r\n");
            buff.append ("} catch (Exception ex) {\r\n");
            buff.append ("\tthrow new RuntimeException (ex);\r\n");
            buff.append ("}\r\n}");
            return buff.toString ();
        }

        public void addBlock (String block) {
            blocks.add (block);
        }
    }

    public File getUserControlClassPath () {
        return userControlClassPath;
    }

    public void setUserControlClassPath (File userControlClassPath) {
        this.userControlClassPath = userControlClassPath;
    }

    public String getCompileClassPath () {
        return compileClassPath;
    }

    public void setCompileClassPath (String compileClassPath) {
        if (compileClassPath.contains ("%20")) compileClassPath = compileClassPath.replaceAll ("%20", " ");
        JasmineCreator2.compileClassPath = compileClassPath;
    }

    public void addUserControl (String name, String className) {
        userControls.put (name, className);
    }

    public void setUserControl (Map<String, String> map) {
        userControls = map;
    }

    public void create (JasmineFileInfo info) throws IOException, ParseException {
        DocumentReader dr = new DocumentReader (reader);
        root = dr.parse ();
        String docType = null;
        for (int i = 0; i < root.getChildren ().length; i++) {
            Tag tag = root.getChildren ()[i];
            if (tag instanceof PageDirective)
                parsePageInfo ((PageDirective) tag);
            if (tag instanceof DocDeclartion)
                docType = "<!" + ((DocDeclartion) tag).getContent ().trim () + '>';
        }

        if (pageInfo == null) {
            throw new ParseException ("Can't find page information.");
        }
        fileInfo = info;

        if (pageInfo.getSuperClassName () != null) {
            try {
                superClass = loadClass (pageInfo.getSuperClassName ());
            } catch (ClassNotFoundException ex) {
                logger.fatal (ex.getMessage (), ex);
                throw new ParseException (ex);
            }
        }

        // init virtual class
        vc.setModifier (Modifier.PUBLIC);
        vc.setName (info.getFileName ());
        vc.setPackageName (info.getPackageName ());
        if (pageInfo.getSuperClassName () == null) {
            if (info.getType () == JasmineFileInfo.TYPE_PAGE) {
                vc.setSuperClassName ("Page");
                vc.addImport ("org.dreamwork.jasmine2.web.controls.Page");
                logger.debug ("preparing to create page");
            } else {
                vc.setSuperClassName ("UserControl");
                vc.addImport ("org.dreamwork.jasmine2.web.controls.UserControl");
                logger.debug ("preparing to create user control");
            }
        } else
            vc.setSuperClassName (pageInfo.getSuperClassName ());
        vc.addImport ("org.dreamwork.peony.DataBinder");
        List list = pageInfo.getImportPackages ();
        for (Object aList : list) {
            String line = (String) aList;
            String[] items = line.split (",");
            for (String item : items) vc.addImport (item.trim ());
        }

        initComponents = new VirtualMethod ();
        initComponents.setModifier (Modifier.PROTECTED);
        initComponents.setReturnType (null);
        initComponents.setName ("initComponents");
        vc.addMethod (initComponents);

        construction = vc.addDefaultConstruction ();

        String contentType = pageInfo.getContentType ();
        if (StringUtil.isEmpty (contentType))
            contentType = "text/plain;charset=utf-8";
        construction.addStatement ("contentType = \"" + JavaCodeFormat.convert (contentType) + "\";");
        String resourceName = pageInfo.getResourceName ();
        if (!StringUtil.isEmpty (resourceName))
            construction.addStatement ("resourceName = \"" + resourceName + "\";");
        if (!StringUtil.isEmpty (pageInfo.getLayout ()))
            construction.addStatement ("layout = \"" + pageInfo.getLayout () + "\";");

        if (!StringUtil.isEmpty (docType))
            initComponents.addStatement (
                    "addControl (new org.dreamwork.jasmine2.web.controls.Literal (\"" +
                            JavaCodeFormat.convert (docType) + "\"));"
            );

        VirtualMethod getUniqueId = new VirtualMethod ();
        getUniqueId.setModifier (Modifier.PUBLIC);
        getUniqueId.setName ("getUniqueId");
        getUniqueId.setReturnType ("String");
        getUniqueId.addStatement ("return \"" + info.getClassName () + "\"");
        vc.addMethod (getUniqueId);

        context.setCreator (this);
        context.setCurrent (vc);
        context.setFileInfo (fileInfo);
        context.setPageInfo (pageInfo);
        context.setInitComponents (initComponents);
        context.setSuperClass (superClass);

        Tag[] children = root.getChildren ();
        stack.push ("this");
        for (Tag child : children) {
            parseTag (child);
        }

        info.createPackageDir ();
        File file = info.getSrcFile ();
        File p = file.getParentFile ();
        if (!p.exists () && !p.mkdirs ())
            throw new IOException ("Can't create folder: " + p.getCanonicalPath ());
        FileOutputStream fos = new FileOutputStream (file);
        try {
            String enc;
            int pos = contentType.indexOf (";");
            if (pos > 0) {
                enc = contentType.substring (pos + 1).trim ();
                pos = enc.indexOf ('=');
                enc = enc.substring (pos + 1).trim ();
            } else enc = "UTF-8";
            OutputStreamWriter osw = new OutputStreamWriter (fos, enc);
            osw.write (vc.toString ());
            osw.flush ();
        } finally {
            fos.flush ();
            fos.close ();
        }

        StringWriter sw = new StringWriter ();
        PrintWriter pw = new PrintWriter (sw, true);
        if (!compileJDT (file.getAbsolutePath (), info.getClassDir ().getAbsolutePath (), pw)) {
            pw.flush ();
            sw.flush ();
            throw new ParseException (sw.getBuffer ().toString ());
        }
    }

    private void parseTextNode (TextNode tag) {
        if (tag instanceof CDATANode) {
            String parentControl = stack.peek ();
            if (parentControl == null) return;
            String content = JavaCodeFormat.convert (tag.toString ());
            content = parentControl + ".addControl (new Literal (\"" + content+ "\"))";
            initComponents.addStatement (content);
        } else if (tag instanceof TaglibDirective) {
            TaglibDirective taglib = (TaglibDirective) tag;
            pageInfo.addUri (taglib.getPrefix (), taglib);
        } else if (!(tag instanceof DocDeclartion)) {
            String parentControl = stack.peek ();
            if (parentControl == null) return;
            if (!(tag instanceof JspDirective)) {
                vc.addImport ("org.dreamwork.jasmine2.web.controls.Literal");
                String content = JavaCodeFormat.convert (tag.getContent ());
                initComponents.addStatement (parentControl + ".addControl (new Literal (\"" + content + "\"))");
            } else if (tag instanceof SimpleExpression) {
                String content = tag.getContent ();
                initComponents.addStatement (parentControl + ".addControl (new Literal (" + content + "))");
            } else if (tag instanceof BindingExpression) {
                String content = tag.getContent ();
                StringBuilder buffer = new StringBuilder (parentControl);
                buffer.append (".addControl (new org.dreamwork.jasmine2.web.controls.DynamicControl () {\r\n");
                buffer.append ("\tprotected Object buildContent () throws Throwable {\r\n");
                buffer.append ("\t\treturn ").append (content).append (";\r\n");
                buffer.append ("\t}\r\n");
                buffer.append ("})");
                initComponents.addStatement (buffer.toString ());
            }
        }
    }

    private void parseServerSideScriptNode (Tag tag) {
        Tag[] children = tag.getChildren ();
        if (children != null) for (Tag child : children) {
            JavaCodeSnipParser parser = new JavaCodeSnipParser (child.toString ());
            parser.parse ();
            List<String> classes = parser.getClasses ();
            for (String aClass : classes)
                vc.addCodeSnip (aClass);
            List<String> functions = parser.getFunctions ();
            for (String function : functions)
                vc.addCodeSnip (function);

            List<String> variables = parser.getVariables ();
            for (String variable : variables)
                vc.addCodeSnip (variable);

            List<String> statements = parser.getStatements ();
            if (statements.size () > 0) {
                if (init == null)
                    init = createInit ();
                for (String statement : statements)
                    init.addBlock (statement);
            }
        }
    }

    private PageInitMethod createInit () {
        init = new PageInitMethod ();
        init.addBlock ("super.init ();\r\n");
        vc.addMethod (init);
        return init;
    }

    @SuppressWarnings ("unchecked")
    public void parseTag (Tag tag) throws ParseException, IOException {
        if (tag.isHTMLComment () || tag.isJSPComment ()) return;
        boolean runAtServer = "server".equalsIgnoreCase (tag.getAttribute ("runat"));
        if (tag.isTextNode ()) {
            parseTextNode ((TextNode) tag);
            return;
        } else if ("script".equalsIgnoreCase (tag.getName ()) && runAtServer) {
            parseServerSideScriptNode (tag);
            return;
        }
        String id = tag.getAttribute ("id");

        boolean needCreateField = true;
        Field f = null;
        if (superClass != null && id != null) {
            try {
                f = getField (superClass, id);
                if (f != null) {
                    needCreateField = !(java.lang.reflect.Modifier.isProtected (f.getModifiers ()) ||
                            java.lang.reflect.Modifier.isPublic (f.getModifiers ()));
                }
            } catch (NoSuchFieldException ex) {
                logger.debug ("Can't find field [" + id + "] from super class. create new one. ");
            }
        }

        VirtualField field = createField (tag, id);
//        boolean isClientScript = field.getType ().equals (ClientScriptControl.class.getCanonicalName ());

        if (needCreateField && runAtServer) { // 定义类变量
            vc.addField (field);
            createInitMethod (field, tag, true);
        } else { // 定义局部变量
            String newString = "()";
            String name = field.getName ();
            if ("org.dreamwork.jasmine2.web.html.GenericHtmlControl".equals (field.getType ()))
                newString = "(\"" + tag.getName () + "\")";
            String stmt = name + " = new " + field.getType () + " " + newString;
            if (f == null) stmt = field.getType () + " " + stmt;
            initComponents.addStatement (stmt);
            createInitMethod (field, tag, false);
        }

        boolean parseChild = true;
        if (tag.getPrefix () != null) {
            String prefix = tag.getPrefix ();
            TaglibDirective taglib = pageInfo.getTaglib (prefix, tag.getName ());
/*
            if (taglib == null)
                throw new ParseException ("Unknown prefix: " + prefix);
*/
            if (taglib != null) {
                String name = taglib.getName ();
                if (name == null) { // web control
                    String uri = taglib.getURI ();
                    ControlConfiguration cc = ConfigurationParser.getControl (uri, tag.getName ());
                    String className = cc.getClassName ();
                    try {
                        Class c = loadClass (className);
                        TemplateSupport ts = (TemplateSupport) c.getAnnotation (TemplateSupport.class);
                        parseChild = ts == null;
                        if (ts != null) {
                            String controlName = field.getName ();
                            TemplateCreator tc = (TemplateCreator) c.getAnnotation (TemplateCreator.class);
                            if (tc != null) {
                                tc.value ().newInstance ().create (c, context, tag, controlName);
                            } else {
                                processTemplateNonCreate (c, getInnerTags (tag), controlName);
                            }
                        }
                    } catch (ParseException ex) {
                        throw ex;
                    } catch (Exception e) {
                        e.printStackTrace ();
                    }
                }
            }
        }

        if (parseChild) {
            Tag[] children = tag.getChildren ();
            stack.push (field.getName ());
            if (children != null && children.length > 0) {
                for (Tag child : children)
                    parseTag (child);
            }
            if (!stack.empty ()) stack.pop ();
        }
    }

    @SuppressWarnings ("unchecked")
    private void processTemplateNonCreate (Class c, Tag template, String controlName) {
        ParseChildren pc = (ParseChildren) c.getAnnotation (ParseChildren.class);
        WebControlTemplate wct = (WebControlTemplate) c.getAnnotation (WebControlTemplate.class);
        Class templateClass = wct.value ();
        if (template != null) {
            String content = JavaCodeFormat.convert (template.toString ());
            initComponents.addStatement (templateClass.getName () + " " + controlName + "_template = new " + templateClass.getName () + " ()");
            initComponents.addStatement ("String " + controlName + "_content = \"" + content + "\"");
            initComponents.addStatement (controlName + "_template.setContent (" + controlName + "_content)");
            if (pc != null && pc.value ()) {
                TemplateParser tp = (TemplateParser) templateClass.getAnnotation (TemplateParser.class);
                String parserName = tp == null ? "org.dreamwork.jasmine2.template.impl.XMLTemplateParser" : tp.value ().getName ();
                initComponents.addStatement (parserName + " " + controlName + "_parser = new " + parserName + " ()");
                initComponents.addStatement ("if (" + controlName + "_parser.validateTemplate (" + controlName + "_content)) " + controlName + "_template.setRootElement(" + controlName + "_parser.parse (" + controlName + "_content))");
            }
            initComponents.addStatement (controlName + "_template.instantiateIn (" + controlName + ")");
        }
    }

    private Tag getInnerTags (Tag tag) {
        Tag[] children = tag.getChildren ();
        if (children == null || children.length == 0) return null;
        Tag template = new Tag ("template");
        for (Tag child : tag.getChildren ()) template.addChild (child);
        return template;
    }

    private String getFieldName (String className, String id) {
        String fieldName;
        if (id == null) {
            Integer I = declareControls.get (className);
            if (I == null) {
                I = 0;
            } else {
                I = I + 1;
            }
            declareControls.put (className, I);
            int pos = className.lastIndexOf ('.');
            if (pos != -1) fieldName = className.substring (pos + 1) + I;
            else fieldName = className + I;

            fieldName = "_auto_" + Character.toLowerCase (fieldName.charAt (0)) + fieldName.substring (1);
        } else {
            fieldName = id;
        }

        return fieldName;
    }

    @SuppressWarnings ("unchecked")
    public VirtualField createField (Tag child, String id) throws ParseException {
        String className, fieldName;
        String prefix = child.getPrefix ();

        if (prefix == null) {
            if ("script".equalsIgnoreCase (child.getName ()) && !"server".equalsIgnoreCase (child.getAttribute ("runnat"))) {
                className = "org.dreamwork.jasmine2.web.html.ClientScriptControl";
            } else
                className = "org.dreamwork.jasmine2.web.html.GenericHtmlControl";
        } else {
            TaglibDirective taglib = pageInfo.getTaglib (prefix, child.getName ());
/*
            if (taglib == null)
                throw new ParseException ("Unknown prefix: " + prefix);
*/
            if (taglib != null) {
                String name = taglib.getName ();
                if (name == null) { // web control
                    String uri = taglib.getURI ();
                    ControlConfiguration cc = ConfigurationParser.getControl (uri, child.getName ());
                    if (cc == null) { // unknown control
                        throw new ParseException ("Unknown control: " + child.getName ());
                    } else {
                        className = cc.getClassName ();
                    }
                } else { // user control
                    className = userControls.get (name);
                }
            } else { // normal xml namespace
                className = "org.dreamwork.jasmine2.web.html.GenericHtmlControl";
            }
        }

        fieldName = getFieldName (className, id);
        VirtualField vf = new VirtualField ();
        vf.setType (className);
        vf.setName (fieldName);
        vf.setModifier (Modifier.PRIVATE);
        return vf;
    }

    private Map<String, String> dynamicGetters = new HashMap<String, String> ();

    private Map getSetters (VirtualField field) throws IOException {
        Map<String, Method> setters = cache.get (field.getType ());
        try {
            if (setters == null) {
                setters = new HashMap<String, Method> ();
                Class clazz = loadClass (field.getType ());
                Method[] methods = clazz.getMethods ();
                for (Method method : methods) {
                    String methodName = method.getName ();
                    Class[] params = method.getParameterTypes ();
                    if (methodName.startsWith ("set") && params.length == 1) {
                        String x = methodName.substring (3).toLowerCase ();
                        if ("id".equalsIgnoreCase (x)) continue;
                        setters.put (x, method);
                    }
                }
                cache.put (field.getType (), setters);
            }
        } catch (ClassNotFoundException ex) {
            logger.warn (ex.getMessage (), ex);
        }

        return setters;
    }

    private void processDynamicAttribute (String attrName, String value, VirtualField field) {
        ExpressionParser parser = new ExpressionParser (value);
        value = parser.parse ();
        String tempName = dynamicGetters.get (value);
        if (tempName == null) {
            tempName = "dynamicGetters" + (dynamicGetters.size () + 1);
            VirtualMethod vm = new VirtualMethod ();
            vm.setReturnType ("java.lang.Object");
            vm.setName (tempName);
            dynamicGetters.put (value, tempName);
            vm.setModifier (Modifier.PUBLIC);
            vm.addStatement ("return " + value);
            vc.addMethod (vm);
        }
        initComponents.addStatement (field.getName () + ".setDynamicAttribute (\"" + attrName + "\", \"" + tempName + "\")");
    }

    private void processAttribute (VirtualField field, String name, String attrName, String value, Map setters) {
        if ("dataSource".equalsIgnoreCase (attrName)) {
            initComponents.addStatement (name + ".setDataSource (findDataSource(" + value + "))");
        } else if ("runat".equalsIgnoreCase (attrName)) {
            value = new String (value.toCharArray (), 1, value.length () - 2);
            value = String.valueOf ("server".equalsIgnoreCase (value.trim ()));
            initComponents.addStatement (name + ".setRunAtServer (" + value + ")");
        } else if ("autopostback".equalsIgnoreCase (attrName)) {
            value = new String (value.toCharArray (), 1, value.length () - 2);
            initComponents.addStatement (name + ".setAutoPostback (" + value + ")");
        } else {
            Method method = (Method) setters.get (attrName.toLowerCase ().trim ());
            String stmt = null;
            if (method != null) {
                Class type = method.getParameterTypes ()[0];
                value = new String (value.toCharArray (), 1, value.length () - 2).trim ();
                value = JavaCodeFormat.convert (value);
                stmt = setAttribute (type, method, value, name);
            }
            if (stmt == null && !isSpecialAttribute (attrName)) {
//                value = TextFormat.htmlAttributeEscape (value);
//                value = TextFormat.cStyleEscape (value, true);
                if (value.indexOf ('\\') > -1)
                    value = value.replace ("\\", "\\\\");
                stmt = field.getName () + ".setAttribute (\"" + attrName + "\", " + value + ")";
            }
            initComponents.addStatement (stmt);
        }
    }

    @SuppressWarnings ("unchecked")
    private String setAttribute (Class type, Method method, String value, String name) {
        String stmt = null;
        if (type.isPrimitive ())
            stmt = name + "." + method.getName () + " (" + value + ")";
        else if (type.isEnum ()) {
            Method m = null;
            try {
                m = type.getMethod ("parse", String.class);
            } catch (NoSuchMethodException e) {
                // ignore
            }

            if (m == null) {
                try {
                    m = type.getMethod ("valueOf", type, String.class);
                } catch (NoSuchMethodException ex) {
                    // ingore
                }
            }
            if (m != null)
                stmt = name + "." + method.getName () + "(" +
                       type.getName () + "." + m.getName () + "(\"" + value + "\"))";
        } else if (type.isAssignableFrom (String.class)) {
            stmt = name + "." + method.getName () + "(\"" + value + "\")";
        } else {
            Class[] parameterTypes = {String.class, Serializable.class, Comparable.class, CharSequence.class};
            for (Class c : parameterTypes) {
                try {
                    type.getConstructor (c);
                    stmt = name + "." + method.getName () + "(new " + type.getName () + "(\"" + value + "\"))";
                    break;
                } catch (NoSuchMethodException e) {
                    // ignore
                }
            }
        }
        return stmt;
    }

    public void createInitMethod (VirtualField field, Tag tag, boolean createNewStatement) throws IOException {
        boolean isClientScriptControl = "org.dreamwork.jasmine2.web.html.ClientScriptControl".equals (field.getType ());
        boolean isGenericHtmlControl = "org.dreamwork.jasmine2.web.html.GenericHtmlControl".equals (field.getType ());
        boolean isLayoutControl = "org.dreamwork.jasmine2.web.controls.layout.ReplaceHolderControl".equals (field.getType ());
        String name = field.getName ();
        if (isLayoutControl)
            name = "replaceHolder";
        if (createNewStatement) {
            if (isGenericHtmlControl)
                initComponents.addStatement (name + " = new org.dreamwork.jasmine2.web.html.GenericHtmlControl (\"" + tag.getName () + "\")");
            else if (isLayoutControl)
                construction.addStatement ("this.replaceHolder = new " + field.getType () + " ()");
            else
                initComponents.addStatement (name + " = new " + field.getType () + " ()");
        }

        if (isGenericHtmlControl && !StringUtil.isEmpty (tag.getPrefix ())) {
            initComponents.addStatement (String.format ("%s.setNamespace (\"%s\")", name, tag.getPrefix ()));
        }

        if (isClientScriptControl) {
            String content = tag.getChildren () [0].toString ();
            tag.clearChild ();

            Matcher m = P_expression.matcher (content);
            while (m.find ()) {
                StringBuffer buffer = new StringBuffer ();
                m.appendReplacement (buffer, "");
                tag.addChild (new TextNode (buffer.toString ()));
                char c = m.group (1).trim ().charAt (0);
                String text = m.group (2).trim ();
                if (c == '=')
                    tag.addChild (new SimpleExpression (text));
                else if (c == '#')
                    tag.addChild (new BindingExpression (text));
            }
            String text = m.appendTail (new StringBuffer ()).toString ();
            tag.addChild (new TextNode (text));
/*
            Tag[] children = tag.getChildren ();
            if (children != null && children.length > 0) {
                StringBuilder builder = new StringBuilder ();
                for (Tag child : children)
                    builder.append (child);
                String text = JavaCodeFormat.convert (builder.toString ());
                initComponents.addStatement (name + ".setInnerText (\"" + text + "\")");
            }
*/
        }

        if (JasmineFileInfo.TYPE_PAGE == fileInfo.getType ())
            initComponents.addStatement (name + ".setId(\"" + field.getName () + "\")");
        else if (JasmineFileInfo.TYPE_USER_CONTROL == fileInfo.getType () && !name.startsWith ("_auto_")) {
            initComponents.addStatement (name + ".setId(getIdPrefix () + \"" + field.getName () + "\")");
        }
        String parentControl = stack.peek ();
        if (parentControl != null)
            initComponents.addStatement (parentControl + ".addControl (" + name + ")");
        if (!name.startsWith ("_auto_") || !isGenericHtmlControl)
            initComponents.addStatement ("directControls.add (\"" + name + "\", " + name + ")");

        String[] attrNames = tag.getAttributeNames ();
        Map setters = getSetters (field);

        if (attrNames != null) for (String attrName : attrNames) {
            String value = tag.getAttribute (attrName).trim ();
            if ("id".equalsIgnoreCase (attrName)) continue;
            if (value.contains ("<%#")) {
                processDynamicAttribute (attrName, value, field);
            } else {
                if (value.contains ("<%=")) {
                    ExpressionParser parser = new ExpressionParser (value);
                    value = parser.parse ();
                } else {
                    Matcher m = p_context_root.matcher (value);
                    if (m.matches ())
                        value = "request.getContextPath () + \"/" + m.group (1) + "\"";
                    else
                        value = "\"" + value + "\"";
                }

                processAttribute (field, name, attrName, value, setters);
            }
        }

        if ("form".equalsIgnoreCase (tag.getName ()) && "server".equalsIgnoreCase (tag.getAttribute ("runat")))
            initComponents.addStatement ("setServerSideForm (" + name + ")");
        else if ("html".equalsIgnoreCase (tag.getName ()))
            initComponents.addStatement ("html = " + name);
        else if ("body".equalsIgnoreCase (tag.getName ()))
            initComponents.addStatement ("body = " + name);
        else if ("head".equalsIgnoreCase (tag.getName ()))
            initComponents.addStatement ("head = " + name);
    }

    public void setReader (Reader reader) {
        this.reader = reader;
    }

    public Tag getRoot () {
        return root;
    }

    private void parsePageInfo (PageDirective pd) throws ParseException {
        if (pageInfo == null) pageInfo = new JasminePageInfo ();

        if (pd.getAttribute ("buffer") != null) {
            String s_buff_size = pd.getAttribute ("buffer");
            try {
                pageInfo.setBuffSize (Integer.parseInt (s_buff_size));
            } catch (NumberFormatException nfe) {
                throw new ParseException ("Buffer attribute only an interger");
            }
        }
        if (pd.getAttribute ("autoFlush") != null) {
            String s_auto_flush = pd.getAttribute ("autoFlush");
            try {
                pageInfo.setAutoFlush (Boolean.valueOf (s_auto_flush.trim ()));
            } catch (Exception ex) {
                throw new ParseException ("autoFlush attribute can only be a boolean");
            }
        }
        if (pd.getAttribute ("contentType") != null) {
            if (pageInfo.getContentType () != null) {
                throw new ParseException ("A page only have a content type.");
            }
            pageInfo.setContentType (pd.getAttribute ("contentType").trim ());
        }

        if (pd.getAttribute ("import") != null) {
            pageInfo.addImportPackages (pd.getAttribute ("import").trim ());
        }

        if (pd.getAttribute ("extends") != null) {
            if (pageInfo.getSuperClassName () != null) {
                throw new ParseException ("A page can only extends a class");
            }
            pageInfo.setSuperClassName (pd.getAttribute ("extends").trim ());
        }

        if (pd.getAttribute ("resource") != null) {
            pageInfo.setResourceName (pd.getAttribute ("resource").trim ());
        }

        if (pd.getAttribute ("layout") != null)
            pageInfo.setLayout (pd.getAttribute ("layout").trim ());
    }

    private boolean compileJDT (String srcFileName, String outdir, PrintWriter pw) {
        org.eclipse.jdt.internal.compiler.batch.Main main = new Main (pw, pw, false);
        String cp = compileClassPath;

        if (userControlClassPath != null && userControlClassPath.exists ())
            cp += File.pathSeparator + this.userControlClassPath.getAbsoluteFile ();
        String[] cmd = {
                "-Xlint:unchecked",
                "-d", outdir,
                "-classpath", cp,
                "-encoding", "utf-8",
                "-source", JasmineContext.JVM_Version,
                "-target", JasmineContext.JVM_Version,
                srcFileName};
        return main.compile (cmd);
    }

    public static boolean isSpecialAttribute (String attrName) {
        for (String special_attr : special_attrs) {
            if (special_attr.equalsIgnoreCase (attrName)) return true;
        }
        return false;
    }

    public Field getField (Class c, String name) throws NoSuchFieldException {
        Field f = null;
        try {
            f = c.getDeclaredField (name);
        } catch (NoSuchFieldException sfe) {
            // ignore;
        }
        if (f != null) return f;

        if (c == Object.class) return null;

        return getField (c.getSuperclass (), name);
//        return ReferenceUtil.getField (c, name);
    }

    public Class loadClass (String name) throws IOException, ClassNotFoundException {
        try {
//            return Class.forName (name);
            return Thread.currentThread ().getContextClassLoader ().loadClass (name);
        } catch (ClassNotFoundException e) {
            // ignore
        }

        if (loader == null) {
            synchronized (locker) {
//                if (loader == null) {
                    URL[] a = new URL[compileUrls.size ()];
                    a = compileUrls.toArray (a);
                    int length = userControlClassPath == null ? a.length : a.length + 1;
                    URL[] t = new URL[length];
                    System.arraycopy (a, 0, t, 0, a.length);
                    if (userControlClassPath != null) t[length - 1] = userControlClassPath.toURI().toURL ();
                    loader = new URLClassLoader (t, Thread.currentThread ().getContextClassLoader ());
//                }
            }
        }

        return loader.loadClass (name);
    }

    public static void setCompileUrls (Collection<URL> urls) {
        synchronized (locker) {
            if (compileUrls == urls) return;
            compileUrls.addAll (urls);
        }
        StringBuilder builder = new StringBuilder ();
        for (URL url : urls) {
            if (builder.length () > 0) builder.append (File.pathSeparatorChar);
            builder.append (url.getFile ());
        }
        compileClassPath = builder.toString ();
    }
}